/*
  Copyright © 2011  Vinícius dos Santos Oliveira

  This file is part of Vinipsmaker's HTTP tools.

  Vinipsmaker's HTTP tools is free software: you can redistribute it and/or
  modify it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  details.

  You should have received a copy of the GNU General Public License along with
  this program.  If not, see <http://www.gnu.org/licenses/>.
  */

#include "requestwindow.h"
#include "ui_requestwindow.h"
#include <QButtonGroup>
#include "headerdialog.h"
#include "plaintextbrowserdialog.h"
#include <QFileDialog>
#include <QInputDialog>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include "iodevicemanager.h"
#include <QDataStream>
#include "urlcodecdialog.h"
#include <QTemporaryFile>

RequestWindow::RequestWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::RequestWindow),
    entityBodyMenuButtonGroup(new QButtonGroup(this)),
    networkAccessManager(new QNetworkAccessManager(this)),
    requestEntityBody(new IODeviceManager(this)),
    reply(NULL),
    responseEntityBody(NULL)
{
    ui->setupUi(this);

    {
        QListWidgetItem *item = new QListWidgetItem;

        item->setText("Host: localhost");
        item->setData(Qt::UserRole, QString("Host"));
        item->setData(Qt::UserRole + 1, QString("localhost"));

        ui->headersListWidget->addItem(item);

        item = new QListWidgetItem;

        item->setText("Connection: close");
        item->setData(Qt::UserRole, QString("Connection"));
        item->setData(Qt::UserRole + 1, QString("close"));

        ui->headersListWidget->addItem(item);
    }

    entityBodyMenuButtonGroup->addButton(ui->textEntityBodyButton);
    entityBodyMenuButtonGroup->addButton(ui->fileEntityBodyButton);
    entityBodyMenuButtonGroup->addButton(ui->noneEntityBodyButton);
}

RequestWindow::~RequestWindow()
{
    if (responseEntityBody)
        delete responseEntityBody;

    delete ui;
}

inline QListWidgetItem * RequestWindow::headerItem(const QString &headerKey)
{
    for (int i = 0;i < ui->headersListWidget->count();++i) {
        QListWidgetItem *currentItem = ui->headersListWidget->item(i);
        if (QString::compare(headerKey,
                             currentItem->data(Qt::UserRole).toString(),
                             Qt::CaseInsensitive) == 0) {
            return currentItem;
        }
    }
    return NULL;
}

void RequestWindow::on_addHeaderButton_clicked()
{
    HeaderDialog *d = new HeaderDialog(this);
    if (d->exec() == QDialog::Accepted) {
        QListWidgetItem *item = new QListWidgetItem;

        item->setText(d->key() + ": " + d->value());
        item->setData(Qt::UserRole, d->key());
        item->setData(Qt::UserRole + 1, d->value());

        ui->headersListWidget->addItem(item);
    }
    delete d;
}

void RequestWindow::on_editHeaderButton_clicked()
{
    QListWidgetItem *item = ui->headersListWidget->currentItem();
    if (item) {
        HeaderDialog *d = new HeaderDialog(this);

        d->setkey(item->data(Qt::UserRole).toString());
        d->setValue(item->data(Qt::UserRole + 1).toString());

        if (d->exec() == QDialog::Accepted) {
            item->setText(d->key() + ": " + d->value());
            item->setData(Qt::UserRole, d->key());
            item->setData(Qt::UserRole + 1, d->value());
        }
    }
}

void RequestWindow::on_removeHeaderButton_clicked()
{
    QListWidgetItem *item = ui->headersListWidget->currentItem();
    if (item) {
        ui->headersListWidget->removeItemWidget(item);
        delete item;
    }
}

void RequestWindow::on_textEntityBodyButton_toggled(bool checked)
{
    if (!checked)
        return;

    ui->entityBodyTextEdit->setEnabled(true);
    ui->entityBodyFileChooserButton->setEnabled(false);
    ui->addContentLengthButton->setEnabled(true);
}

void RequestWindow::on_fileEntityBodyButton_toggled(bool checked)
{
    if (!checked)
        return;

    ui->entityBodyTextEdit->setEnabled(false);
    ui->entityBodyFileChooserButton->setEnabled(true);
    ui->addContentLengthButton->setEnabled(true);
}

void RequestWindow::on_noneEntityBodyButton_toggled(bool checked)
{
    if (!checked)
        return;

    ui->entityBodyTextEdit->setEnabled(false);
    ui->entityBodyFileChooserButton->setEnabled(false);
    ui->addContentLengthButton->setEnabled(false);
}

void RequestWindow::on_entityBodyFileChooserButton_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this);
    if (fileName.isNull())
        return;

    ui->entityBodyFileLabel->setText(fileName);
}

void RequestWindow::on_addContentLengthButton_clicked()
{
    QListWidgetItem *item = headerItem("Content-Length");

    if (!item) {
        item = new QListWidgetItem;
        item->setData(Qt::UserRole, QString("Content-Length"));

        ui->headersListWidget->addItem(item);
    }

    QString contentLengthHeaderValue;

    if (ui->textEntityBodyButton->isChecked()) {
        contentLengthHeaderValue = QString::number(ui->entityBodyTextEdit->toPlainText().size());
    } else {
        contentLengthHeaderValue = QString::number(QFile(ui->entityBodyFileLabel->text()).size());
    }

    item->setText(item->data(Qt::UserRole).toString() + ": " + contentLengthHeaderValue);
    item->setData(Qt::UserRole + 1, contentLengthHeaderValue);
}

void RequestWindow::on_urlCodecButton_clicked()
{
    UrlCodecDialog *d = new UrlCodecDialog(this);
    d->exec();
    delete d;
}

void RequestWindow::on_saveRequestButton_clicked()
{
    QString fileName = QFileDialog::getSaveFileName(this);
    if (fileName.isNull())
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly))
        return;

    QDataStream stream(&file);
    // Qt 4.6
    stream.setVersion(12);

    // request line
    stream << ui->methodComboBox->currentText()
           << ui->uriLineEdit->text()
           << ui->versionComboBox->currentText();

    // the number of headers ...
    quint8 headersNumber = ui->headersListWidget->count();
    stream << headersNumber;

    // ... and the headers
    for (int i = 0;i < headersNumber;++i) {
        QListWidgetItem *item = ui->headersListWidget->item(i);
        // the key
        stream << item->data(Qt::UserRole).toString()
                  //and the value
               << item->data(Qt::UserRole + 1).toString();
    }

    // the entity body state + data, if any
    // 0 - no entity
    // 1 - plain text entity
    // 2 - file entity
    if (ui->textEntityBodyButton->isChecked()) {
        stream << quint8(1);
    } else if (ui->fileEntityBodyButton->isChecked()) {
        stream << quint8(2);
    } else {
        stream << quint8(0);
    }


    stream << ui->entityBodyTextEdit->toPlainText()
           << ui->entityBodyFileLabel->text();

    // and the host
    stream << hostUrl;
}

void RequestWindow::on_loadRequestButton_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this);
    if (fileName.isNull())
        return;

    QFile file(fileName);
    if (!file.open(QIODevice::ReadOnly))
        return;

    QDataStream stream(&file);
    // Qt 4.6
    stream.setVersion(12);

    // request line
    {
        QString buffer;

        stream >> buffer;
        ui->methodComboBox->lineEdit()->setText(buffer);

        stream >> buffer;
        ui->uriLineEdit->setText(buffer);

        stream >> buffer;
        ui->versionComboBox->lineEdit()->setText(buffer);
    }

    ui->headersListWidget->clear();

    // the number of headers ...
    quint8 headersNumber;
    stream >> headersNumber;

    // ... and the headers
    for (int i = 0;i < headersNumber;++i) {
        QListWidgetItem *item = new QListWidgetItem;

        QString buffer;

        // the key
        stream >> buffer;
        item->setData(Qt::UserRole, buffer);

        //and the value
        stream >> buffer;
        item->setData(Qt::UserRole + 1, buffer);

        item->setText(item->data(Qt::UserRole).toString()
                      + ": "
                      + item->data(Qt::UserRole + 1).toString());

        ui->headersListWidget->addItem(item);
    }

    // the entity body state
    {
        quint8 entityBodyState;
        stream >> entityBodyState;

        switch (entityBodyState) {
        case 0:
            ui->noneEntityBodyButton->setChecked(true);
            break;
        case 1:
            ui->textEntityBodyButton->setChecked(true);
            break;
        case 2:
            ui->fileEntityBodyButton->setChecked(true);
        }
    }

    // the entity body data
    {
        QString buffer;

        stream >> buffer;
        ui->entityBodyTextEdit->setText(buffer);

        stream >> buffer;
        ui->fileEntityBodyButton->setText(buffer);
    }

    // and the host
    stream >> hostUrl;
}

void RequestWindow::on_requestButton_clicked()
{
    QNetworkRequest request;

    // get the url to the request
    {
        QString urlString;
        QString defaultUrl;

        {
            QListWidgetItem *item = headerItem("Host");
            if (item) {
                defaultUrl = "http://"
                        + item->data(Qt::UserRole + 1).toString()
                        + "/";
            } else {
                defaultUrl = "http://localhost/";
            }
        }

        if (hostUrl.isNull()){
            bool ok;

            urlString = QInputDialog::getText(this, "URL...",
                                              tr("Enter url:"),
                                              QLineEdit::Normal,
                                              defaultUrl, &ok);

            if (!ok)
                return;
        } else {
            bool ok;
            urlString = QInputDialog::getText(this, "URL...",
                                              tr("Enter url:"),
                                              QLineEdit::Normal,
                                              hostUrl, &ok);

            if (!ok)
                return;
        }

        QUrl url = urlString;

        if (!url.isValid())
            return;

        url.setPath(ui->uriLineEdit->text());
        request.setUrl(url);

        if (urlString != defaultUrl)
            hostUrl = urlString;
    }

    // set headers
    for (int i = 0;i < ui->headersListWidget->count();++i) {
        QListWidgetItem *currentItem = ui->headersListWidget->item(i);
        request.setRawHeader(currentItem->data(Qt::UserRole).toString().toUtf8(),
                             currentItem->data(Qt::UserRole + 1).toString().toUtf8());
    }

    // another request was already made, but the reply wasn't received yet
    if (reply) {
        disconnect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()));
        delete reply;
        reply = NULL;
        delete responseEntityBody;
        responseEntityBody = NULL;
    }

    // send the request
    requestEntityBody->clearAll();
    if (ui->textEntityBodyButton->isChecked()) {
        reply = networkAccessManager->sendCustomRequest(request,
                                                        ui->methodComboBox->currentText().toUtf8(),
                                                        requestEntityBody->stringDevice(ui->entityBodyTextEdit->toPlainText()));
    } else if (ui->fileEntityBodyButton->isChecked() && QFile(ui->entityBodyFileLabel->text()).size()) {
        reply = networkAccessManager->sendCustomRequest(request,
                                                        ui->methodComboBox->currentText().toUtf8(),
                                                        requestEntityBody->fileDevice(ui->entityBodyFileLabel->text()));
    } else {
        reply = networkAccessManager->sendCustomRequest(request,
                                                        ui->methodComboBox->currentText().toUtf8(),
                                                        NULL);
    }

    // signal connections
    connect(reply, SIGNAL(finished()), this, SLOT(onNetworkReplyFinished()));

    // clear the previous response state
    ui->responseHeadersListWidget->clear();
}

void RequestWindow::onNetworkReplyFinished()
{
    {
        //! @todo fill version with the real HTTP version received from the reply
        QString version = "HTTP/?.?";
        QString statusCode = QString::number(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
        QString reasonPhrase = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray();
        ui->statusLineLabel->setText(version + " " + statusCode + " " + reasonPhrase);
    }

    {
        QList<QNetworkReply::RawHeaderPair> headers = reply->rawHeaderPairs();
        for (int i = 0;i < headers.size();++i) {
            ui->responseHeadersListWidget->addItem(headers[i].first + ": " + headers[i].second);
        }
    }

    responseEntityBody = new QTemporaryFile;
    responseEntityBody->open();
    responseEntityBody->write(reply->readAll());
}

void RequestWindow::on_showAsTextButton_clicked()
{
    if (!responseEntityBody)
        //! @todo show a error-info dialog
        return;

    PlainTextBrowserDialog *d = new PlainTextBrowserDialog(this);
    responseEntityBody->seek(0);
    d->setText(responseEntityBody->readAll());
    d->exec();
    delete d;
}

void RequestWindow::on_saveEntityBodyResponseButton_clicked()
{
    if (!responseEntityBody)
        //! @todo show a error-info dialog
        return;

    QString fileName = QFileDialog::getSaveFileName(this);
    if (fileName.isNull())
        return;

    responseEntityBody->copy(fileName);
}
